/*© 2010-2011 mOcean Mobile. A subsidiary of Mojiva, Inc. All Rights Reserved.*/
package com.adserver.adview;

import com.adserver.adview.interfaces.OnAdDownload;
import com.adserver.adview.interfaces.OnAdNavigateBanner;
import com.adserver.adview.interfaces.OnAdWebViewClosing;
import com.adserver.adview.utils.BareBonesBrowserLaunch;
import com.adserver.adview.utils.Utils;

import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.Timer;
import java.util.logging.Level;

/**
 * Viewer of advertising.
 * Following parameters are defined automatically, if they are equal NULL:
 * latitude - Latitude.
 * longitude - Longitude.
 * carrier - Carrier name.
 * country - Country of visitor (for example: US).
 * useragent - The browser user agent of the device making the request.
 */
public class AdServerView extends JEditorPane implements HyperlinkListener {

/// Properties start

    // Advertiser id (if both AdvertiserId and GroupCode are specified then install notification is enabled).
    // Used for initAdServerView only
    private String advertiserId;
    public final String parameter_advertiser_id = "advertiser_id";

    // Group code (if both AdvertiserId and GroupCode are specified then install notification is enabled).
    // Used for initAdServerView only
    private String groupCode;
    public final String parameter_group_code = "group_code";

    // Carrier name.
    private String carrier;
    public final String parameter_carrier = "carrier";

    // Area code of a user. For US only.
    private String area;
    public final String parameter_area = "area";
    // City of the device user (with state). For US only.
    private String city;
    public final String parameter_city = "city";
    // Country of visitor. See codes here (http://www.mojiva.com/docs/iso3166.csv). Will override country detected by IP.
    private String country;
    public final String parameter_country = "country";
    // Zip/Postal code of user. For US only.
    private String zip;
    public final String parameter_zip = "zip";
    // The id of the zone of publisher site.
    private String zone;
    public final String parameter_zone = "zone";
    // The id of the publisher site.
    private String site;
    public final String parameter_site = "site";
    // Metro code of a user. For US only.
    private String metro;
    public final String parameter_metro = "metro";
    // Region of visitor.
    // See codes for US and Canada here (http://www.mojiva.com/docs/iso3166_2.csv),
    // others - here (http://www.mojiva.com/docs/fips10_4.csv).
    private String region;
    public final String parameter_region = "region";

    // Keywords to search ad delimited by commas. Ex. "keywords=music,games"
    private String keywords;
    public final String parameter_keywords = "keywords";

    // Latitude.
    private String latitude;
    public final String parameter_latitude = "lat";
    // Longitude.
    private String longitude;
    public final String parameter_longitude = "long";

    // The minimum width of advertising.
    private int minSizeX;
    public final String parameter_min_size_x = "min_size_x";
    // The minimum height of advertising
    private int minSizeY;
    public final String parameter_min_size_y = "min_size_y";
    // The maximum width of advertising
    private int maxSizeX;
    public final String parameter_max_size_x = "size_x";
    // The maximum height of advertising
    private int maxSizeY;
    public final String parameter_max_size_y = "size_y";

    // Premium type: non-premium.
    public static final int PREMIUM_STATUS_NON_PREMIUM = 0;
    // Premium type: premium only.
    public static final int PREMIUM_STATUS_PREMIUM = 1;
    // Premium type: premium and non-premium.
    public static final int PREMIUM_STATUS_BOTH = 2;
    // Set Filter by premium. Can be used only by premium publishers.
    private int premium;
    public final String parameter_premium = "premium";

    // Default setting is test mode where, if the ad code is properly installed, the ad response is "Test MODE".
    private String isTest;
    public final String parameter_test = "test";

    // Background color in borders (for example: #ffffff).
    private String backgroundColor;
    public final String parameter_background = "paramBG";

    // Text color.
    private String textColor;
    public final String parameter_text = "paramLINK";

    // Browser user agent of the device making the request.
    private String userAgent;
    public final String parameter_userAgent = "ua";

    // Quantity of ads, returned by a server. Maximum value is 5.
    private int count = 1;
    public final String parameter_count = "count";

    // Type of advertisement: text only.
    public static final int ADS_TYPE_TEXT_ONLY = 1;
    // Type of advertisement: image only.
    public static final int ADS_TYPE_IMAGES_ONLY = 2;
    // Type of advertisement: image and text.
    public static final int ADS_TYPE_TEXT_AND_IMAGES = 3;
    // Type of advertisement: SMS ad. SMS will be returned in XML.
    public static final int ADS_TYPE_SMS = 4;
    // Set Type of advertisement (ADS_TYPE_TEXT_ONLY - text only,
    // ADS_TYPE_IMAGES_ONLY - image only, ADS_TYPE_TEXT_AND_IMAGES - image and text,
    // ADS_TYPE_SMS - SMS ad). SMS will be returned in XML.
    private int adstype = ADS_TYPE_TEXT_AND_IMAGES;
    public final String parameter_adstype = "adstype";

    // Parameter excampaigns should allow excluding the list of campaigns from the result by ID.
    private String excampaigns;
    public final String parameter_excampaigns = "excampaigns";

    // SDK version
    public static final String SDK_VERSION = "2.0.1";
    private final String version = SDK_VERSION;
    public final String parameter_version = "version";

    // Connection speed. -1 - not defined, 0 - low (gprs, edge), 1 - fast (3g, wifi).
    private int connectionSpeed = -1;
    public final String parameter_connection_speed = "connection_speed";

    // Set to 1, return image size (width and height) in html.
    private int sizeRequired = 0;
    public final String parameter_size_required = "size_required";

    // Device id (as IMEA for mobile device)
    protected final String udid = Utils.md5(System.getProperty("os.name") +
            System.getProperty("os.version") +
            System.getProperty("os.arch") +
            System.getProperty("java.runtime.version"));
    public final String parameter_device_id = "udid";

    // Custom user parameters
    private Hashtable<String, String> customParameters;

    // URL of ad server.
    private String adServerURL = "http://ads.mocean.mobi/ad";

    // The path of the resource, which will be shown during advertising loading
    private String defaultImage;

    // The flag which operates advertising opening.
    // False - Ad opens in an external browser. True - Ad opening at the help of a adNavigateBanner.
    private boolean isInternalBrowser = false;

    // Banner refresh interval (in seconds).
    private int updateTime = 120;

    private final String defaultEncoding = "UTF-8";

    private boolean isActivatedComponent;
    private AdServerWindowListener windowListener;

    // 0 - not used, 1 - errors, 2 - errors & info, 3 - errors & info & http
    private int logLevel = 3;

/// Properties finish

/// Objects & Threads & interfaces start

    private ContentThread contentThread = null;
    private Timer reloadTimer;

    // Processed events:
    // event AdDownloadBegin
    // event AdDownloadEnd
    // event AdDownloadError
    private OnAdDownload adDownload;

    // The interface for advertising opening.
    private OnAdNavigateBanner adNavigateBanner;

    // The interface for internal browser closing.
    private OnAdWebViewClosing adWebViewClosing;

    protected Popup popup;

    private boolean isFirstRun = true;

    /// Threads & interfaces finish

/// Constructors start

    /**
     * Creation of viewer of advertising.
     *
     * @param site - The id of the publisher site.
     * @param zone - The id of the zone of publisher site.
     * @throws AdServerException Exception when param initialization
     */
    public AdServerView(String site, String zone) throws AdServerException {
        super();

        // initialization of gui part
        super.setContentType("text/html");
        super.setOpaque(false);
        super.setEditable(false);
        super.addHyperlinkListener(this);

        setSite(site);
        setZone(zone);

        windowListener = new AdServerWindowListener(this);

        this.reloadTimer = new Timer();

        // process run in thread to get autodetect params
        AutoDetectParameters.getInstance().start();

        if (contentThread == null) {
            contentThread = new ContentThread(this, true);
        }
    }

/// Constructors finish

/// Methods start

    public void releaseResources() {
        if (adWebViewClosing != null) {
            if (getPage() == null) {
                adWebViewClosing.process("");
            } else {
                adWebViewClosing.process(getPage().toExternalForm());
            }
        }

        if (reloadTimer != null) {
            try {
                reloadTimer.cancel();
            } catch (Exception e) {
            }
        }

        reloadTimer = null;

        if (contentThread != null) {
            try {
                contentThread.interrupt();
            } catch (Exception e) {
            }
        }

        contentThread = null;

        AutoDetectParameters.getInstance().releaseResources();

        AdServerRequest.getInstance().releaseResources();
    }

    /**
     * Immediately update banner contents.
     */
    public void update() {
        Logger.getInstance().logText(Level.INFO, "update()");

        if (isActivatedComponent) {
            new ContentThread(this, false).start();
        } else {
            Logger.getInstance().logText(Level.INFO, "update(), adServerView is not visible.");
        }
    }

    protected void showAdContent(String content) {
        super.setSize(maxSizeX, maxSizeY);
        setText(content);
    }

    public synchronized String createURL() throws UnsupportedEncodingException {
        AutoDetectParameters.getInstance().copyParamsTo(this);

        StringBuilder builderToString = new StringBuilder();
        builderToString.append(adServerURL).append("?key=1");
        appendParameter(builderToString, parameter_carrier, carrier);
        appendParameter(builderToString, parameter_area, area);
        appendParameter(builderToString, parameter_city, city);
        appendParameter(builderToString, parameter_country, country);
        appendParameter(builderToString, parameter_zip, zip);
        appendParameter(builderToString, parameter_zone, zone);
        appendParameter(builderToString, parameter_site, site);
        appendParameter(builderToString, parameter_metro, metro);
        appendParameter(builderToString, parameter_region, region);
        appendParameter(builderToString, parameter_keywords, keywords);
        appendParameter(builderToString, parameter_latitude, latitude);
        appendParameter(builderToString, parameter_longitude, longitude);
        if (minSizeX > 0) {
            appendParameter(builderToString, parameter_min_size_x, Integer.toString(minSizeX));
        }
        if (minSizeY > 0) {
            appendParameter(builderToString, parameter_min_size_y, Integer.toString(minSizeY));
        }
        if (maxSizeX > 0) {
            appendParameter(builderToString, parameter_max_size_x, Integer.toString(maxSizeX));
        }
        if (maxSizeY > 0) {
            appendParameter(builderToString, parameter_max_size_y, Integer.toString(maxSizeY));
        }
        appendParameter(builderToString, parameter_premium, Integer.toString(premium));
        appendParameter(builderToString, parameter_test, isTest);
        appendParameter(builderToString, parameter_background, backgroundColor);
        appendParameter(builderToString, parameter_userAgent, userAgent);
        appendParameter(builderToString, parameter_count, Integer.toString(count));
        appendParameter(builderToString, parameter_adstype, Integer.toString(adstype));
        appendParameter(builderToString, parameter_text, textColor);
        appendParameter(builderToString, parameter_excampaigns, excampaigns);
        appendParameter(builderToString, parameter_version, version);
        appendParameter(builderToString, parameter_connection_speed, Integer.toString(connectionSpeed));
        appendParameter(builderToString, parameter_size_required, Integer.toString(sizeRequired));
        appendParameter(builderToString, parameter_device_id, udid);
        appendParameters(builderToString, customParameters);
        Logger.getInstance().logText(Level.INFO, "CreateURL:\n" + builderToString.toString());
        return builderToString.toString();
    }

    private void appendParameter(StringBuilder builderToString, String parameter, String value) throws UnsupportedEncodingException {
        if (value != null) {
            builderToString.append("&").append(URLEncoder.encode(parameter, defaultEncoding))
                    .append("=").append(URLEncoder.encode(value, defaultEncoding));
        }
    }

    private void appendParameters(StringBuilder builderToString, Map<String, String> parameters) throws UnsupportedEncodingException {
        if (parameters != null) {
            Set<String> keySet = parameters.keySet();
            for (String param : keySet) {
                String value = parameters.get(param);
                if (value != null) {
                    builderToString.append("&").append(URLEncoder.encode(param, defaultEncoding))
                            .append("=").append(URLEncoder.encode(value, defaultEncoding));
                }
            }
        }
    }

/// Methods stop

/// Objects access methods start

    // Get interface for advertising opening.
    public OnAdNavigateBanner getAdNavigateBanner() {
        return adNavigateBanner;
    }

    // Set interface for advertising opening.
    public void setAdNavigateBanner(OnAdNavigateBanner adNavigateBanner) {
        this.adNavigateBanner = adNavigateBanner;
    }

    // Get interface for advertising downloading.
    public OnAdDownload getAdDownload() {
        return adDownload;
    }

    // Set interface for advertising downloading.
    public void setAdDownload(OnAdDownload adDownload) {
        this.adDownload = adDownload;
    }

    public OnAdWebViewClosing getAdWebViewClosing() {
        return adWebViewClosing;
    }

    public void setAdWebViewClosing(OnAdWebViewClosing adWebViewClosing) {
        this.adWebViewClosing = adWebViewClosing;
    }

/// Objects access methods stop

/// Properties access methods start

    public String getAdServerURL() {
        return adServerURL;
    }

    public void setAdServerURL(String adServerURL) {
        this.adServerURL = adServerURL;
    }

    public String getAdvertiserId() {
        return advertiserId;
    }

    public void setAdvertiserId(String advertiserId) {
        this.advertiserId = advertiserId;
    }

    public String getGroupCode() {
        return groupCode;
    }

    public void setGroupCode(String groupCode) {
        this.groupCode = groupCode;
    }

    public String getCarrier() {
        return carrier;
    }

    public void setCarrier(String carrier) {
        this.carrier = carrier;
    }

    public String getArea() {
        return area;
    }

    public void setArea(String area) {
        this.area = area;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getZip() {
        return zip;
    }

    public void setZip(String zip) {
        this.zip = zip;
    }

    public String getZone() {
        return zone;
    }

    public void setZone(String zone) throws AdServerException {
        if (zone != null && Integer.parseInt(zone) > 0) {
            this.zone = zone;
        } else
            throw new AdServerException("Incorrect zone value: " + zone);
    }

    public String getSite() {
        return site;
    }

    public void setSite(String site) throws AdServerException {
        if (site != null && Integer.parseInt(site) > 0) {
            this.site = site;
        } else
            throw new AdServerException("Incorrect site value: " + site);
    }

    public String getMetro() {
        return metro;
    }

    public void setMetro(String metro) {
        this.metro = metro;
    }

    public String getRegion() {
        return region;
    }

    public void setRegion(String region) {
        this.region = region;
    }

    public String getKeywords() {
        return keywords;
    }

    public void setKeywords(String keywords) {
        this.keywords = keywords;
    }

    public String getLatitude() {
        return latitude;
    }

    public void setLatitude(String latitude) {
        this.latitude = latitude;
    }

    public String getLongitude() {
        return longitude;
    }

    public void setLongitude(String longitude) {
        this.longitude = longitude;
    }

    public int getMinSizeX() {
        return minSizeX;
    }

    public void setMinSizeX(int minSizeX) throws AdServerException {
        if (minSizeX > 0) {
            this.minSizeX = minSizeX;
        } else
            throw new AdServerException("Incorrect minSizeX value: " + minSizeX);
    }

    public int getMinSizeY() {
        return minSizeY;
    }

    public void setMinSizeY(int minSizeY) throws AdServerException {
        if (minSizeY > 0) {
            this.minSizeY = minSizeY;
        } else
            throw new AdServerException("Incorrect minSizeY value: " + minSizeY);
    }

    public int getMaxSizeX() {
        return maxSizeX;
    }

    public void setMaxSizeX(int maxSizeX) throws AdServerException {
        if (maxSizeX > 0) {
            this.maxSizeX = maxSizeX;
        } else
            throw new AdServerException("Incorrect maxSizeX value: " + maxSizeX);
    }

    public int getMaxSizeY() {
        return maxSizeY;
    }

    public void setMaxSizeY(int maxSizeY) throws AdServerException {
        if (maxSizeY > 0) {
            this.maxSizeY = maxSizeY;
        } else
            throw new AdServerException("Incorrect maxSizeY value: " + maxSizeY);
    }

    public int getPremium() {
        return premium;
    }

    public void setPremium(int premium) throws AdServerException {
        if (premium >= PREMIUM_STATUS_NON_PREMIUM && premium <= PREMIUM_STATUS_BOTH) {
            this.premium = premium;
        } else
            throw new AdServerException("Incorrect premium value: " + premium);
    }

    public boolean isTest() {
        return isTest != null;
    }

    public void setTest(boolean test) {
        if (test) {
            isTest = "1";
        } else {
            isTest = null;
        }
    }

    public String getBackgroundColor() {
        return backgroundColor;
    }

    public void setBackgroundColor(String backgroundColor) {
        this.backgroundColor = backgroundColor;
    }

    public String getTextColor() {
        return textColor;
    }

    public void setTextColor(String textColor) {
        this.textColor = textColor;
    }

    public String getUserAgent() {
        return userAgent;
    }

    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) throws AdServerException {
        if (count >= 1 && premium <= 5) {
            this.count = count;
        } else
            throw new AdServerException("Incorrect count value: " + count);
    }

    public int getAdstype() {
        return adstype;
    }

    public void setAdstype(int adstype) throws AdServerException {
        if (premium >= ADS_TYPE_TEXT_ONLY && premium <= ADS_TYPE_SMS) {
            this.adstype = adstype;
        } else
            throw new AdServerException("Incorrect adstype value: " + adstype);
    }

    public String getExcampaigns() {
        return excampaigns;
    }

    public void setExcampaigns(String excampaigns) {
        this.excampaigns = excampaigns;
    }

    public String getVersion() {
        return version;
    }

    public int getConnectionSpeed() {
        return connectionSpeed;
    }

    public void setConnectionSpeed(int connectionSpeed) {
        this.connectionSpeed = connectionSpeed;
    }

    public int getSizeRequired() {
        return sizeRequired;
    }

    public void setSizeRequired(int sizeRequired) {
        this.sizeRequired = sizeRequired;
    }

    public Hashtable<String, String> getCustomParameters() {
        return customParameters;
    }

    public void setCustomParameters(Hashtable<String, String> customParameters) {
        this.customParameters = customParameters;
    }

    public String getDefaultImage() {
        return defaultImage;
    }

    public void setDefaultImage(String defaultImage) {
        this.defaultImage = defaultImage;
    }

    public boolean isInternalBrowser() {
        return isInternalBrowser;
    }

    public void setInternalBrowser(boolean internalBrowser) {
        isInternalBrowser = internalBrowser;
    }

    public int getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(int updateTime) throws AdServerException {
        if (updateTime > 0) {
            this.updateTime = updateTime;
        } else
            throw new AdServerException("Incorrect updateTime value: " + updateTime);
    }

    protected boolean isActivatedComponent() {
        return isActivatedComponent;
    }

    public AdServerWindowListener getWindowListener() {
        return windowListener;
    }

    protected boolean isFirstRun() {
        return isFirstRun;
    }

    protected void setFirstRun(boolean firstRun) {
        isFirstRun = firstRun;
    }

    public int getLogLevel() {
        return logLevel;
    }

    public void setLogLevel(int logLevel) {
        this.logLevel = logLevel;
        // 0 - not used, 1 - errors, 2 - errors & info, 3 - errors & info & http
        switch (logLevel) {
            case 0:
                Logger.getInstance().setLevel(Level.OFF);
                break;
            case 1:
                Logger.getInstance().setLevel(Level.SEVERE);
                break;
            case 2:
                Logger.getInstance().setLevel(Level.WARNING);
                break;
            case 3:
                Logger.getInstance().setLevel(Level.INFO);
                break;
        }
    }

/// Properties access methods finish

/// ReloadTask class management start

    public void initializeReloadTask() {
        if (updateTime > 0) {
            reloadTimer.schedule(new ReloadTask(this), updateTime * 1000);
            Logger.getInstance().logText(Level.INFO,
                    "initializeReloadTask for (s) " + updateTime);
        }
    }

    class ReloadTask extends TimerTask {
        private AdServerView adServerView;

        public ReloadTask(AdServerView adServerView) {
            this.adServerView = adServerView;
        }

        @Override
        public void run() {
            boolean repeat = true;
            Logger.getInstance().logText(Level.INFO, "runContentThread from ReloadTask");
            ContentThread.runContentThread(adServerView, repeat);
        }

    }

/// ReloadTask class management finish

/// Override methods start

    public void hyperlinkUpdate(HyperlinkEvent event) {
        if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
            if (isInternalBrowser) {
                if (adNavigateBanner == null || adNavigateBanner.click(event.getURL().toString())) {
                    try {
                        if (popup != null) {
                            popup.hide();
                        }
                        setPage(event.getURL());
                    } catch (IOException e) {
                        Logger.getInstance().logText(Level.SEVERE,
                                "hyperlinkUpdate error for url: " + event.getURL() + ". Message: " + e.getMessage());
                    }
                }
            } else {
                BareBonesBrowserLaunch.openURL(event.getURL().toString());
            }
        }
    }

/// Override methods finish

/// Window actions start

    public void OnWindowOpened() {
        isActivatedComponent = true;
        contentThread.start();
    }

    public void OnWindowClosing() {
        isActivatedComponent = false;
        releaseResources();
    }

    public void OnWindowActivated() {
        isActivatedComponent = true;
    }

    public void OnWindowDectivated() {
        isActivatedComponent = false;
    }

/// Window methods stop
}